package org.zhaya.sdk.util;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyCodeSource;
import org.codehaus.groovy.ast.stmt.Statement;
import org.codehaus.groovy.ast.stmt.WhileStatement;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.SecureASTCustomizer;
import org.codehaus.groovy.syntax.Types;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * groovy factory
 *
 * @author jackl
 */
public class GroovyFactory {
    private static final ConcurrentMap<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();
    private static final GroovyClassLoader GROOVY_CLASS_LOADER = GroovyFactory.getSecureGroovyClassLoader();

    private Object getObject(URL url) throws InstantiationException, IllegalAccessException {
        Class<?> clazz = GROOVY_CLASS_LOADER.parseClass(new GroovyCodeSource(url));
        Object instance = getObject(clazz);
        if (instance != null) {
            return instance;
        }
        throw new IllegalArgumentException("read groovy file exception");
    }

    private static Object getObject(Class<?> clazz) throws InstantiationException, IllegalAccessException {
        if (clazz != null) {
            return clazz.newInstance();
        }
        return null;
    }

    public static Object getObject(String sourceCode) throws InstantiationException, IllegalAccessException {
        Class<?> codeSourceClass = getCodeSourceClass(sourceCode);
        return getObject(codeSourceClass);
    }

    private static Class<?> getCodeSourceClass(String codeSource) {
        try {
            // md5
            String md5Str = DigestUtil.md5Hex(codeSource.getBytes());

            Class<?> clazz = CLASS_CACHE.get(md5Str);
            if (Objects.isNull(clazz)) {
                clazz = GROOVY_CLASS_LOADER.parseClass(codeSource);
                CLASS_CACHE.putIfAbsent(md5Str, clazz);
            }
            return clazz;
        } catch (Exception e) {
            return GROOVY_CLASS_LOADER.parseClass(codeSource);
        }
    }

    public static GroovyClassLoader getSecureGroovyClassLoader() {
        // 创建SecureASTCustomizer
        final SecureASTCustomizer secure = new SecureASTCustomizer();
        // forbid use closure .
        secure.setClosuresAllowed(true);
        List<Integer> tokensBlacklist = new ArrayList<>();
        // add keyword while  to bl
        tokensBlacklist.add(Types.KEYWORD_WHILE);
        secure.setTokensBlacklist(tokensBlacklist);
        // set indirect check
        secure.setIndirectImportCheckEnabled(true);
        // add imports bl
        List<String> list = new ArrayList<>();
        list.add(JSONObject.class.getName());
        //add System to bl
        list.add(System.class.getName());
        //add Runtime to bl
        list.add(Runtime.class.getName());
        secure.setImportsBlacklist(list);
        List<Class<? extends Statement>> statementBlacklist = new ArrayList<>();
        statementBlacklist.add(WhileStatement.class);
        secure.setStatementsBlacklist(statementBlacklist);
        final CompilerConfiguration config = new CompilerConfiguration();
        config.addCompilationCustomizers(secure);
        GroovyClassLoader groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader(), config);
        return groovyClassLoader;
    }

}